#include "Neuron.h"

// Constrói um neurônio passando seu índice, o nº de variáveis de entrada, a função de ativação e o aprendizado
// Os pesos do neurônio serão calculados randomicamente
Neuron::Neuron(uint index, uint nInVars, Activation& cActivation, Learning& cLearning) :
        activation(cActivation), learning(cLearning), weights(nInVars + 1)
{
    // Garante que o número de variáveis de entrada seja maior que 0
    assert(nInVars > 0);

    this->index = index;
    this->nInVars = nInVars;

    // Gera uma semente aleatória
    srand(time(NULL));

    // Seta os pesos inicias com valores aleatórios
    resetWeights();
}

// Constrói um neurônio passando seu índice, o nº de variáveis de entrada, a função de ativação e o aprendizado
// Os pesos do neurônio serão carregados de uma entrada
Neuron::Neuron(uint index, uint nInVars, Activation& cActivation, Learning& cLearning, vdouble& w) :
        activation(cActivation), learning(cLearning), weights(nInVars + 1)
{
    // Garante que o número de variáveis de entrada seja igual à quantidade de pesos -1 (bias)
    assert(nInVars == w.size()-1);

    this->index = index;
    this->nInVars = nInVars;

    // Seta os pesos
    for(uint i = 0; i < w.size(); i++)
        weights.push_back(w.get(i));
}

Neuron::~Neuron()
{
}

// Retorna um valor aleatório baixo entre 0 e 0.1
double Neuron::getRandom()
{
    return rand() * 0.1 / (double) (RAND_MAX);
}

// Retorna o peso da variável de entrada de índice i
vdouble& Neuron::getWeightVector()
{
    return weights;
}

// Retorna o peso da variável de entrada de índice i
double Neuron::getWeight(int i)
{
    return weights.get(i);
}

// Seta o peso da variável de entrada de índice i
void Neuron::setWeight(int i, double w)
{
    weights.set(i, w);
}

// Reseta os pesos
void Neuron::resetWeights()
{
    for(uint i = 0; i < weights.size(); i++)
        weights.set(i, getRandom());
}

// Retorna o output do neurônio dada uma entrada
double Neuron::getOutput(vdouble& input)
{
    // Garante que o número de entradas seja o mesmo passado pelo construtor
    assert(nInVars == input.size());

    double sum = 0;

    // Realiza a função de propagação (somatória das entradas com seus pesos)
    for(uint i = 0; i < nInVars; i++)
        sum += weights.get(i) * input.get(i);
    sum += weights.get(nInVars);

    // Chama a função de ativação
    return activation(sum);
}

// Realiza o treinamento do neurônio passando a entrada e o erro feito
void Neuron::train(vdouble& input, double error)
{
    // Garante que o número de entradas seja o mesmo passado pelo construtor
    assert(nInVars == input.size());

    // Chama o aprendizado para cada peso do neurônio
    for(uint i = 0; i < nInVars; i++)
        weights.inc(i, learning(input.get(i), error));
    weights.inc(nInVars, learning(1, error));
}

// Imprime os pesos
void Neuron::printWeights()
{
    for(uint i = 0; i <= nInVars; i++)
        cout << weights.get(i) << " ";
}

